﻿// *********************************************************************
// [DCOM Productions]
// [Copyright (C) DCOM Productions All rights reserved.]
// *********************************************************************

namespace BitFlex.Xdf {
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.IO;

    /// <summary>
    /// Provides means to read and parse Xdf strings into usable objects
    /// </summary>
    public class XdfReader {
        /// <summary>
        /// Initializes a new instance of the BitFlex.Xdf.XdfReader class
        /// </summary>
        public XdfReader() {
        }

        #region Constants

        private const string REGED_KEYVALUEPAIR     = @"[0-9a-zA-Z]*\s*?=\s*?[^\r]*";
        private const string REGEX_SECTION_BEGIN    = @"[0-9a-zA-Z]*:Begin";
        private const string REGEX_SECTION_END      = @"[0-9a-zA-Z]*:End";

        #endregion

        #region Methods

        /// <summary>
        /// Gets the name of the fully qualified Xdf Section
        /// </summary>
        /// <param name="xdf">The Xdf of the section whose name is to be found</param>
        /// <returns>Returns the name that was found, if any</returns>
        public string GetSectionName(string xdf) {
            Match sectionMatch = Regex.Match(xdf, REGEX_SECTION_BEGIN);
            if (sectionMatch.Success) {
                string retVal = sectionMatch.Value;
                retVal = retVal.Substring(0, retVal.IndexOf(':'));
                return retVal;
            }
            else {
                throw new BitFlex.Xdf.XdfException(Properties.Resources.InvalidXdfString);
            }
        }

        /// <summary>
        /// Loads Xdf from the specified Xdf string
        /// </summary>
        /// <param name="xdf">The Xdf string to load</param>
        /// <returns>Returns the <see cref="XdfSection"/> element</returns>
        public XdfSection LoadFrom(string xdf) {
            if (ValidateSection(xdf)) {
                return ReadSection(xdf);
            }
            else {
                throw new BitFlex.Xdf.XdfException(Properties.Resources.InvalidXdfString);
            }
        }

        /// <summary>
        /// Loads Xdf from the specified file on disk
        /// </summary>
        /// <param name="directory">The directory on disk that contains the file to load</param>
        /// <param name="file">The name of the file to load</param>
        /// <returns>Returns the <see cref="XdfSection"/> element</returns>
        public XdfSection LoadFrom(string directory, string file) {
            string xdfstr = File.ReadAllText(Path.Combine(directory, file));
            if (ValidateSection(xdfstr)) {
                return ReadSection(xdfstr);
            }
            else {
                throw new BitFlex.Xdf.XdfException(Properties.Resources.InvalidXdfString);
            }
        }

        /// <summary>
        /// Reads the specified Xdf string and parses a <see cref="XdfKey"/> object from it
        /// </summary>
        /// <param name="xdf">The xdf string containing the Xdf Key to be read</param>
        /// <returns>Returns the <see cref="XdfKey"/> that was read, if any</returns>
        public XdfKey ReadKey(string xdf) {
            Match keyMatch = Regex.Match(xdf, REGED_KEYVALUEPAIR);
            if (keyMatch.Success) {
                string name = keyMatch.Value.Substring(0, keyMatch.Value.IndexOf('='));
                name = name.TrimEnd(' ');
                XdfKey retVal = new XdfKey(name);
                string value = keyMatch.Value.Remove(0, keyMatch.Value.IndexOf('=') + 1);
                value = value.TrimStart(' ');
                retVal.Value = value;
                return retVal;
            }
            else {
                throw new BitFlex.Xdf.XdfException(Properties.Resources.InvalidXdfKey);
            }
        }

        /// <summary>
        /// Reads all contained sections in the xdf string recursively
        /// </summary>
        /// <param name="xdf">The xdf string that contains the Xdf Section to be read</param>
        /// <returns>Returns the <see cref="XdfSection"/> that was read, if any</returns>
        public XdfSection ReadSection(string xdf) {
            if (ValidateSection(xdf)) {
                string[] rows = xdf.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                XdfSection rootSection = new XdfSection(GetSectionName(rows[0]));
                do {
                    Match beginMatch = Regex.Match(xdf, REGEX_SECTION_BEGIN);
                    beginMatch = beginMatch.NextMatch();
                    if (beginMatch.Success) {
                        Match endMatch = Regex.Match(xdf, string.Format("{0}:End", GetSectionName(beginMatch.Value)));
                        if (endMatch.Success) {
                            string sectionXdf = xdf.Substring(beginMatch.Index, (endMatch.Index + endMatch.Length) - beginMatch.Index);
                            xdf = xdf.Remove(beginMatch.Index, (endMatch.Index + endMatch.Length) - beginMatch.Index);
                            XdfSection section = ReadSection(sectionXdf);
                            rootSection.Sections.Add(section);
                        }
                        else {
                            throw new BitFlex.Xdf.XdfException(string.Format("There is a missing section ending at index {0}.", endMatch.Index));
                        }
                    }
                    else {
                        break;
                    }
                } while (true);
                MatchCollection keyMatches = Regex.Matches(xdf, REGED_KEYVALUEPAIR);
                foreach (Match item in keyMatches) {
                    XdfKey key = ReadKey(item.Value);
                    rootSection.Keys.Add(key);
                }
                return rootSection;
            }
            else {
                throw new BitFlex.Xdf.XdfException("The specified xdf did not contain a valid section.");
            }
        }

        /// <devdoc>
        /// Validates an Xdf section to determine whether its format is valid
        /// </devdoc>
        private bool ValidateSection(string xdf) {
            string[] rows = xdf.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
            if (Regex.Match(rows[0], REGEX_SECTION_BEGIN).Success) {
                if (Regex.Match(rows[rows.Length - 1], REGEX_SECTION_END).Success) {
                    return true;
                }
                else {
                    return false;
                }
            }
            else {
                return false;
            }
        }

        #endregion
    }
}